<?php
/**
 * @category system
 * @package db
 * @subpackage db.adpater.mysql
 * @version 1.0.0
 * @author nam nguyen <nam.ngvan@gmail.com>
 * @license GPL 2.0
 *
 */
class Db_Adapter_Mysql extends DbMysql
{

	/**
	 * support balance
	 * @var balancing mode
	 */
	protected $_replicate = false;

	/**
	 * failover connect, try to connect x time before throw error.
	 * @var bool
	 */
	protected $_reconnect = 0;

	/**
	 * current query result
	 * @var result
	 */
	protected $_result = NULL;

	/**
	 * active connection to
	 * @var PDO
	 */
	protected $_master = NULL;

	/**
	 * connection to slave
	 * @var PDO
	 */
	protected $_slave = NULL;

	/**
	 * @var bool
	 */
	protected $_slaveConnected = FALSE;

	/**
	 * @var bool
	 */
	protected $_masterConnected = FALSE;
	/**
	 * @var BOOL
	 */
	protected $_profiling = FALSE;

	/**
	 * validate configure for fast connect later.
	 * @param array $config
	 * @return array
	 */
	protected function _validateConfig($config)
	{
		$config = array_merge(array(
			'host' => 'localhost',
			'username' => '',
			'password' => '',
			'dbname' => '',
			'port' => 3306,
			'options' => array(),
		), $config);

		$config['options'][PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES utf8';
		$config['options'][PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
		$config['options'][PDO::ATTR_DEFAULT_FETCH_MODE] = PDO::FETCH_ASSOC;
		$config['dsn'] = sprintf('mysql:host=%s;port=%s;dbname=%s', $config['host'], $config['port'], $config['dbname']);
		return $config;
	}

	/**
	 * global configuration.
	 * @param   string   $name
	 */
	protected function __construct($name)
	{

		$config = Agilet::loadConfig('mysql.php', $name);

		if (!isset($config['master']))
		{
			throw new Agilet_Exception("master offset required.");
		}

		$config['master'] = $this -> _validateConfig($config['master']);

		if (isset($config['replicate']))
		{
			if (!is_array($config['slaves']))
			{
				throw new Agilet_Exception("slaves offset required with replicate");
			}

			/**
			 * try to valdiate configuration at first be cause will will random READ on
			 * slaves.
			 */
			foreach ($config['slaves'] as $index => $slave)
			{
				$config['slaves'][$index] = $this -> _validateConfig($config['slaves'][$index]);
			}
			$this -> _replicate = TRUE;
		}

		$this -> _name = $name;

		$this -> _config = $config;

		self::$_inst[$name] = $this;
	}

	/**
	 * connect to master
	 * @return DbMysql
	 */
	public function connectMaster()
	{
		if ($this -> _master)
		{
			return;
		}

		try
		{
			$config = $this -> _config['master'];
			$this -> _master = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
		}
		catch(Exception $e)
		{
			throw new Exception("can not connect to database with given information. " . $e -> getMessage());
		}

		return $this -> _master;
	}

	/**
	 * @return PDO
	 */
	public function connectSlave()
	{
		if ($this -> _slave)
		{
			return $this -> _slave;
		}

		if ($this -> _replicate)
		{

			$config = $this -> _config['slaves'][mt_rand(0, count($this -> _config['slaves']))];

			try
			{
				$this -> _slave = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
			}
			catch(Exception $e)
			{
				throw new Exception("can not connect to database with given information.");

			}
		}

		if (null == $this -> _slave)
		{
			$this -> _master OR $this -> connectMaster();

			$this -> _slave = $this -> _master;
		}

		return $this -> _slave;
	}

	/**
	 * execute query
	 * always execute command to master, not allow execute action to master.
	 * if you want o run a select query, please use query instead.
	 * @param string $sql  SQL QUERY: SELECT, UPDATE, DELETE
	 *
	 */
	public function execute($sql)
	{
		$this -> _masterConnected OR $this -> connectMaster();

		$this -> _result = $this -> _master -> exec($sql);

		return $this;
	}

	/**
	 * query data
	 */
	public function query($sql, $slave = 1)
	{
		try
		{
			if ($this -> _replicate && $slave)
			{
				$this -> _slaveConnected OR $this -> connectSlave();
				$this -> _result = $this -> _slave -> query($sql);
			}
			else
			{
				$this -> _masterConnected || $this -> connectMaster();
				$this -> _result = $this -> _master -> query($sql);
			}
		}
		catch(Exception $e)
		{
			throw new Db_Query_Exception('Query exception ' . $e -> getMessage());

		}
		return $this;
	}

	/**
	 * another command is used with slave mode
	 */
	public function command($sql)
	{
		$sql = '';
		return $this -> execute($sql);
	}

	public function insert($table, $value)
	{
		$sql = '';
		return $this -> execute($sql);
	}

	public function delete()
	{
		$sql = '';

		return $this -> execute($sql);
	}

	public function update()
	{
		$sql = '';
		return $this -> execute($sql);
	}

	public function batchInsert()
	{
		$sql = '';
		return $this -> execute($sql);
	}

	public function beginTransaction()
	{
		$this -> _masterConnected or $this -> connectMaster();

		$this -> _master -> beginTransaction();

		return $this;
	}

	public function commit()
	{
		$this -> _masterConnected or $this -> connectMaster();

		$this -> _master -> commit();

		return $this;

	}

	public function rollback()
	{
		$this -> _masterConnected or $this -> connectMaster();
		$this -> _master -> rollBack();
		return $this;
	}

	public function findOne($sql = null)
	{
		if ($sql)
		{
			$this -> query($sql);
		}

		if (is_resource($this -> _result))
		{
			return array();
		}
		return $this -> _result -> fetch();
	}

	public function find($sql = null)
	{
		if ($sql)
		{
			$this -> query($sql);
		}

		if (is_resource($this -> _result))
		{
			return array();
		}

		$result = array();

		while ($row = $this -> _result -> fetch(PDO::FETCH_ASSOC))
		{
			$result[] = $row;
		}

		return $result;
	}

	public function findCol()
	{
		if (is_resource($this -> _result))
		{
			return null;
		}

		$result = array();

		while ($row = $this -> _result -> fetch(PDO::FETCH_COLUMN))
		{
			$result[] = $row;
		}

		return $result;

	}

	public function fetchCell()
	{

		if (is_resource($this -> _result))
		{
			return null;
		}

		$row = $this -> _result -> fetch(PDO::FETCH_COLUMN);
		return $row[0];

	}

	public function getQueryResult()
	{
		return $this -> _result;
	}

	public function freeQueryResult()
	{
		$this -> _result = null;
		return $this;
	}

}
